/* lcnd/main.c 
 * 
 * This file is part of lcnd. 
 * 
 * lcnd is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * lcnd is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with lcnd. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <signal.h>

#include <lcnd/lcn.h>
#include <lcnd/lcnd.h>


/* copied from vccd, and it saves my time :-). 
 *
 * this programme --- lcnd, is a terminal-reader of the LCTT 
 * (eg linux.cn translation team), and i love working in the terminal, 
 * that's also why i made vcc. 
 *
 * we use a server and a client because offline reader is not interesting: 
 * there are no differences between 'git clone'. the client requests an 
 * article, and it reads into the memory by mmap or other allocation functions 
 * --- and then open the 'default' (in fact, my 'default', eg vim) editor to 
 * read the file. it saves the file to /tmp, and can cp it into the user's 
 * directory if the user does want. 
 */

int 			fd;
int 			next_fd;

#define POLLFDS 	1024
struct pollfd 		*fds;

static int usage(void) {
	fprintf(stderr, "Usage: lcnd (no argument)\n");

	return 1;
}

int becomes_daemon(void) {
	int pid;

	if ((pid = fork())) 
		/* then prompt was there again */
		exit(0);

	umask(0);
//	chdir("/");

	close(0);

	if (unlikely(open("/var/log/lcnd.log", O_WRONLY | O_CREAT, 0644) < 0)) {
		perror("open(/var/run/lcnd.log)");

		exit(1);
	}

	close(1);
	close(2);

	(void) dup(0);
	(void) dup(0);

	setsid();

	if (unlikely(fd = open("/var/run/lcnd.pid", O_WRONLY | O_CREAT, 0644)) < 0) {
		perror("open(/var/run/lcnd.pid)");

		exit(1);
	}

	dprintf(fd, "%d\n", getpid());
	close(fd);

	/* fd 3 is now /var/run/lcndd.pid */
	return 0;
}

static int init_socket(void) {
	struct sockaddr_in addr;
	int i;

	if (unlikely((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)) {
		perror("socket()");

		exit(1);
	}

	memset(&addr, 0, sizeof(struct sockaddr_in));

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	addr.sin_port = htons(LCND_PORT);

	if (unlikely(bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0)) {
		perror("bind()");

		exit(1);
	}

	if (unlikely(listen(fd, SOMAXCONN) < 0)) {
		perror("listen()");

		exit(1);
	}

	if (!(fds = amalloc(sizeof(struct pollfd) * POLLFDS))) {
		lcnd_log("*** malloc() failed\n");

		exit(1);
	}

	memset(fds, 0, sizeof(struct pollfd) * POLLFDS);

	for (i = 0; i < POLLFDS; i++) 
		fds[i].fd = -1;

	fds[0].fd = fd;
	fds[0].events = POLLIN;

	next_fd = 1;

	return fd;
}

void sigpipe_handler(int nr) {
	(void) nr;

	lcnd_log("--- SIGPIPE\n");
}

static int setup_signals(void) {
	struct sigaction act;

	memset(&act, 0, sizeof(struct sigaction));
	act.sa_handler = sigpipe_handler;
	
	sigaction(SIGPIPE, &act, NULL);

	return 0;
}


int main(int argc, const char **argv) {
	__uint32_t 		size;
	int 			usr_fd, nfds_total, nfds_ready, i;
	char 			buf[128];

	(void) argv;

	if (unlikely(argc != 1)) 
		return usage();

//	becomes_daemon();
	init_socket();
	setup_signals();
	lcnd_init();

	nfds_total = 1;

	for (;;) {
		nfds_ready = poll(fds, nfds_total, -1);

		if (unlikely(nfds_ready == -1)) {
			perror("poll()");

			break;
		}

		if (fds[0].revents & POLLIN) {
			size = sizeof(struct sockaddr_in);

			if (unlikely(usr_fd = accept(fd, NULL, NULL)) < 0) {
				perror("accept()");

				continue;
			}

			lcnd_new_connection(usr_fd);

			if (next_fd >= POLLFDS) {
				for (next_fd = 1; fds[next_fd].fd == -1 && next_fd < POLLFDS; next_fd++) 
					;

				if (next_fd == POLLFDS) 
					lcnd_log("*** connection queue full. \n");
			}

			fds[next_fd].fd = usr_fd;
			fds[next_fd].events = POLLIN;

			next_fd++;
			nfds_total++;
		}

		for (i = 1; i < nfds_total; i++) {
			if (fds[i].fd == -1) 
				continue;

			if (fds[i].revents & POLLIN) {
				size = read(fds[i].fd, buf, 128);
				
				if (!size) {
					lcnd_close_connection(fds[i].fd);

					close(fds[i].fd);
					fds[i].fd = -1;

					continue;
				}

				lcnd_do_request(fds[i].fd, buf);
				fds[i].events = POLLIN;
			}

			/* the only difference between vccd and lcnd is, lcnd never 
			 * use a 'lcnd_write_packets' because it sends packets it should 
			 * send at that moment because vccd is busy but lcnd isn't, it's 
			 * always free and users won't think 'it's too slow to open an article' 
			 * because they spend most time reading the article. */

			fds[i].revents = 0;
		}
	}

	afree(fds, POLLFDS * sizeof(struct pollfd));

	return 0;
}

